﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using Microsoft.Win32;
using Tisn;

namespace Rad_Studio_Keygen
{
    public class RadKeygen
    {
        private const string StrMap = "ABC2DE34FGHJKLM5NPQRST6U7VWX8YZ9";
        private static readonly byte[] ByteMap = {0x00, 0x07, 0x0E, 0x09, 0x1C, 0x1B, 0x12, 0x15, 0x38, 0x3F, 0x36, 0x31, 0x24, 0x23, 0x2A, 0x2D, 0x70, 0x77, 0x7E, 0x79, 0x6C, 0x6B, 0x62, 0x65, 0x48, 0x4F, 0x46, 0x41, 0x54, 0x53, 0x5A, 0x5D, 0xE0, 0xE7, 0xEE, 0xE9, 0xFC, 0xFB, 0xF2, 0xF5, 0xD8, 0xDF, 0xD6, 0xD1, 0xC4, 0xC3, 0xCA, 0xCD, 0x90, 0x97, 0x9E, 0x99, 0x8C, 0x8B, 0x82, 0x85, 0xA8, 0xAF, 0xA6, 0xA1, 0xB4, 0xB3, 0xBA, 0xBD, 0xC7, 0xC0, 0xC9, 0xCE, 0xDB, 0xDC, 0xD5, 0xD2, 0xFF, 0xF8, 0xF1, 0xF6, 0xE3, 0xE4, 0xED, 0xEA, 0xB7, 0xB0, 0xB9, 0xBE, 0xAB, 0xAC, 0xA5, 0xA2, 0x8F, 0x88, 0x81, 0x86, 0x93, 0x94, 0x9D,
    0x9A, 0x27, 0x20, 0x29, 0x2E, 0x3B, 0x3C, 0x35, 0x32, 0x1F, 0x18, 0x11, 0x16, 0x03, 0x04, 0x0D, 0x0A, 0x57, 0x50, 0x59, 0x5E, 0x4B, 0x4C, 0x45, 0x42, 0x6F, 0x68, 0x61, 0x66, 0x73, 0x74, 0x7D, 0x7A, 0x89, 0x8E, 0x87, 0x80, 0x95, 0x92, 0x9B, 0x9C, 0xB1, 0xB6, 0xBF, 0xB8, 0xAD, 0xAA, 0xA3, 0xA4, 0xF9, 0xFE, 0xF7, 0xF0, 0xE5, 0xE2, 0xEB, 0xEC, 0xC1, 0xC6, 0xCF, 0xC8, 0xDD, 0xDA, 0xD3, 0xD4, 0x69, 0x6E, 0x67, 0x60, 0x75, 0x72, 0x7B, 0x7C, 0x51, 0x56, 0x5F, 0x58, 0x4D, 0x4A, 0x43, 0x44, 0x19, 0x1E, 0x17, 0x10, 0x05, 0x02, 0x0B, 0x0C, 0x21, 0x26, 0x2F, 0x28, 0x3D, 0x3A, 0x33, 0x34, 0x4E, 0x49, 0x40, 0x47, 0x52,
    0x55, 0x5C, 0x5B, 0x76, 0x71, 0x78, 0x7F, 0x6A, 0x6D, 0x64, 0x63, 0x3E, 0x39, 0x30, 0x37, 0x22, 0x25, 0x2C, 0x2B, 0x06, 0x01, 0x08, 0x0F, 0x1A, 0x1D, 0x14, 0x13, 0xAE, 0xA9, 0xA0, 0xA7, 0xB2, 0xB5, 0xBC, 0xBB, 0x96, 0x91, 0x98, 0x9F, 0x8A, 0x8D, 0x84, 0x83, 0xDE, 0xD9, 0xD0, 0xD7, 0xC2, 0xC5, 0xCC, 0xCB, 0xE6, 0xE1, 0xE8, 0xEF, 0xFA, 0xFD, 0xF4, 0xF3};
        public static string GenerateSerialNumber()
        {
            int i, v1, v2, v3, v4, v5, v6, v7, v8, v9;
            string SumValue;
            byte[] ByteArray = new byte[20];
            var random = new Random();
            v1 = 0;
            v2 = 0;
            v3 = 0;
            v4 = 0;
            v5 = 1;
            v6 = 8217;
            v7 = 53;
            v8 = random.Next(0, 32) << 8;
            v8 ^= random.Next(0, 32);
            v8 %= 0x10000;
            SumValue = string.Format("{0}", v1 + v2 + v3 + v4 + v5 + v6 + v7 + v8);
            v9 = 0;

            for (i = 0; i < SumValue.Length; i++)
            {
                v9 ^= ByteMap[(int)SumValue[i]];
            }
            ByteArray[0] = (byte)(((v8 >> 1) & 8) | ((v8 >> 5) & 4) | (2 * v5 & 2));
            ByteArray[1] = (byte)(((v7 >> 1) & 16) | ((v7 >> 4) & 8) | ((v6 >> 5) & 2) | ((v6 >> 8) & 1));
            ByteArray[2] = (byte)((2 * v7 & 16) | (8 * v8 & 8) | ((v5 >> 1) & 4) | ((v6 >> 4) & 2) | (v3 & 1));
            ByteArray[3] = (byte)(4 * v5 & 16);
            ByteArray[4] = (byte)((4 * v9 & 16) | ((v6 >> 4) & 8));
            ByteArray[5] = (byte)((8 * v4 & 8) | ((v8 >> 1) & 4) | ((v8 >> 12) & 2));
            ByteArray[6] = (byte)(((v9 >> 3) & 8) | ((v8 >> 4) & 4) | (2 * v1 & 2));
            ByteArray[7] = (byte)(((v8 >> 11) & 16) | ((v8 >> 7) & 8) | (4 * v6 & 4) | ((v5 >> 3) & 2));
            ByteArray[8] = (byte)(((v8 >> 7) & 16) | ((v6 >> 1) & 1));
            ByteArray[9] = (byte)((4 * v6 & 16) | (v9 & 8) | (v8 & 4));
            ByteArray[10] = (byte)((v8 >> 9) & 8);
            ByteArray[11] = (byte)((4 * v9 & 8) | (4 * v9 & 4) | (v8 & 2) | ((v8 >> 5) & 1));
            ByteArray[12] = (byte)((v8 >> 8) & 1);
            ByteArray[13] = (byte)(((v6 >> 7) & 16) | ((v9 >> 7) & 1));
            ByteArray[14] = (byte)((2 * v7 & 2) | ((v7 >> 1) & 1));
            ByteArray[15] = (byte)((v6 & 8) | ((v6 >> 2) & 4) | ((v8 >> 9) & 1));
            ByteArray[16] = (byte)((16 * v2 & 16) | (2 * v7 & 8) | ((v5 >> 1) & 1));
            ByteArray[17] = (byte)((v9 >> 3) & 2);
            ByteArray[18] = (byte)((v7 & 16) | ((v6 >> 6) & 8) | ((v6 >> 8) & 4) | ((v8 >> 13) & 2) | ((v9 >> 5) & 1));
            ByteArray[19] = (byte)(((v6 >> 9) & 16) | ((v7 >> 3) & 8) | ((v6 >> 11) & 2));

            var Result = "";
            for (i = 0; i < ByteArray.Length; i++)
            {
                if (i == 4 || i == 10 || i == 16)
                    Result += "-";
                Result += StrMap[ByteArray[i]];
            }
            return Result;
        }
        public static string getComputerName()
        {
            return Environment.MachineName;
        }

        private const string KeyMap = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890X";
        public static uint GetKey()
        {
            uint Result = 0xED864640;
            var ComputerName = getComputerName();
            var Key = ComputerName;

            Key = Key.ToUpper();
            for (var i = 0; i < Key.Length; i++)
            {
                var j = 0;
                for (j = 0; j < KeyMap.Length; j++)
                {
                    if (Key[i] == KeyMap[j])
                    {
                        break;
                    }
                }
                if (j >= KeyMap.Length)
                    Result += 16 * 88;
                else
                    Result += 16 * (uint)KeyMap[36 - j - 1];
            }
            return Result;
        }
        public static void EnCryptoFile(byte[] bytes, uint key, string fileName)
        {
            var bs = EnCryptoFile(bytes, key);
            File.WriteAllBytes(fileName, bs);
        }

        public static byte[] EnCryptoFile(byte[] bytes, uint key)
        {
            var kk = key;
            var bs = EnCrypto(bytes, ref kk);
            var result = BitConverter.GetBytes(kk.Revert());
            result = result.Concat(BitConverter.GetBytes(bs.Length.Revert())).Concat(bs).ToArray();
            return result;
        }
        public static byte[] EnCrypto(byte[] bytes, ref uint key)
        {
            var v2 = key;
            for (var i = 0; i < bytes.Length; i++)
            {
                bytes[i] = (byte)(bytes[i] ^ ((v2 >> 24) & 0xFF));
                uint v5 = bytes[i];
                if ((v5 & 0x80) == 0x80)
                    v5 |= 0xFFFFFF00;
                v5 ^= v2;
                v5 = (v5 << 8) ^ v5;
                v5 = (v5 << 16) ^ v5;
                v5 = (v5 << 24) ^ v5;
                v2 = v5;
            }
            key = v2;
            return bytes;
        }
        public static byte[] DeCryptoFile(string fileName, uint key)
        {
            var bs = File.ReadAllBytes(fileName);
            return DeCryptoFile(bs, key);
        }
        public static byte[] DeCryptoFile(byte[] bytes, uint key)
        {
            var dwVerify = bytes.ToUInt32().Revert();
            var dwSize = bytes.ToUInt32(4).Revert();
            var bs = bytes.Skip(8).Take((int)dwSize).ToArray();
            var dec = DeCrypto(bs, ref key);
            if (dwVerify == key)
            {
                return dec;
            }
            else
            {
                return null;
            }
        }

        public static byte[] DeCrypto(byte[] bytes, ref uint key)
        {
            byte[] pBuf = (byte[])bytes.Clone();

            var eax = key;

            for (var i = 0; i < pBuf.Length; i++)
            {
                uint esi = pBuf[i];
                if ((esi & 0x80) == 0x80)
                    esi |= 0xFFFFFF00;
                var ebx = (eax >> 24) & 0xFF;
                pBuf[i] = (byte)(pBuf[i] ^ ebx);
                eax ^= esi;
                ebx = eax << 8;
                eax ^= ebx;
                ebx = eax << 16;
                eax ^= ebx;
                ebx = eax << 24;
                eax ^= ebx;
            }
            key = eax;
            return pBuf;
        }
        public static string GetRegistrationCode()
        {
            var Result = "";
            var FileName = "";
            BinaryReader ms = null;
            var AppDataPath = Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData);
            if (AppDataPath != "")
            {
                FileName = AppDataPath + @"\Embarcadero\.licenses\.cg_license";
                if (File.Exists(FileName))
                {
                    ms = new BinaryReader(new FileStream(FileName, FileMode.Open));
                }
                else
                {
                    FileName = AppDataPath + @"\Embarcadero\.cg_license";
                    if (File.Exists(FileName))
                    {
                        ms = new BinaryReader(new FileStream(FileName, FileMode.Open));
                    }
                }
            }
            if (ms != null && ms.BaseStream.Length > 8)
            {
                ms.BaseStream.Seek(0, SeekOrigin.Begin);
                var dwVerify = ms.ReadUInt32().Revert();
                var dwSize = ms.ReadUInt32().Revert();

                byte[] pBuf = ms.ReadBytes((int)dwSize);

                var eax = GetKey();

                pBuf = DeCrypto(pBuf, ref eax);

                eax &= 0x7FFFFFFF;

                if (eax == dwVerify)
                {
                    var i = 0;
                    for (i = 0; i < dwSize; i++)
                    {
                        if (pBuf[i] == 36)
                            break;
                    }
                    i++;
                    var j = 0;
                    while (i < dwSize && pBuf[i] != 13 && j < 10)
                    {
                        Result += (char)pBuf[i];
                        i++;
                        j++;
                    }
                }
                ms.Close();
            }
            return Result;
        }

        const string ModStr = "8EBD9E688D7106E57BCF63D41BADCE133FEB4CDB718F48F7BF39F6A26EB60BAE" + "0E930DC984FDED2537750C9DCFBB87D7AC7F3AA4D65D9E35C2D277BCB0ECDCA0" + "2D7DAE739AC8BCAE86914F6E77C17A82C77438421FC315DC38F09C7E840AF41E" + "663C5562222E661ED22578A234B58481F862CEABF477C89AE70F15134F83BC7E" + "C2EF57E7274EB74353DE22283113485D9803D4050EF46DB1467EE9D066B104EB" + "385D3C36BD29B58E237E22C0BE66D450BDFCED524481B6DCE3F83BBEC547F926" + "AD23057504DEDB9723EBFD26218167AAC79485FF608F8881D9A6AF5C57BE9A2F" + "B52047ABA92F806955580517F6D147BA1FD5DB3EEF1CEE4CA250D1C0FA824CD9";
        const string ExpStr = "7E8325B1791B628766F2EB82057E4895DB234C1D7B4B09DB3B8BBE433D68F075" + "36C9B38096F51088D9DC4E7058BBD7AC9A60B1B383A3BA23E026F6A53112DE80" + "C191115BB9268DC509D424D8BE1FA7DBDDB7EE5CFD15C57C48A349B1008B4CCE" + "DCC240D31784945260E3814612FD871242FA203F5C1006A6F47FF3A807E3B4DE" + "39535FB5523ABED7B4337606E69245EC13BF9B553FD6F45B0FD290D7CBBEB8C8" + "DF2252DE7EB6A83A679873CC9842B52A093ED00742F11CD23CB5278873253E79" + "0E30B16AC72B7ACF9824B568ED971D768B95CA9D4C9A40C884542B8696AADF58" + "184CE6376E51451EF8D266ECA691ECAB25E15AA8E527312755A55C2B7D390AD9";
        public static bool GenerateLicenseFile(string SerialNumber, string RegistrationCode, RadStudioVersion RadStudioVersion, ref string FileName)
        {
            if (SerialNumber.Trim() == "" || RegistrationCode.Trim() == "")
                return false;
            var Slip = RadLicense.CreateLicenseText(SerialNumber, RegistrationCode, RadStudioVersion);
            var Len = (Slip.Length.Revert());

            byte[] Tmp = new byte[0];
            var tt = BitConverter.GetBytes(Len);
            Tmp = Tmp.Concat(tt).ToArray();
            Tmp = Tmp.Concat(Slip.ToBytes()).ToArray();
            SHA1 sha1 = SHA1.Create();
            var tmpS = sha1.ComputeHash(Tmp).ToHex();
            tmpS = "01" + new string('F', 66) + "00" + tmpS;

            var Modulus = ModStr.HexToBytes();
            var Exponent = ExpStr.HexToBytes();

            BigInteger encData = new BigInteger(tmpS.HexToBytes());
            var decData = encData.modPow(new BigInteger(Exponent), new BigInteger(Modulus));
            var tmps = decData.getBytes().ToBase64();

            Slip = Slip.Replace("e.sign\n0\n",
    "e.sign\nCgeEeu66fCgQJBaqKQwwyiqyHYb22nc2VZRmQVasSDnZAtB/QTLt0CYdgdN16XCz/Nt032fMwTsytchG0l2UeA==\n");
            Slip = Slip.Replace("e.sign2\n0\n",
    "e.sign2\nJWKzOwTKBL+zOP5wrouG5ta/mH+Fvsgb7hb8oJTzu4r3gK/6sh95zKAWKiydqsgvV9pxPXTAlkxv9wAecqJKTQ==\n");
            Slip = Slip.Replace("e.sign3\n0\n", "e.sign3\n" + tmps + "\n");

            var tmpSlip = Slip.ToBytes();
            uint v2 = 0xE7F931C2;

            tmpSlip = EnCrypto(tmpSlip, ref v2);

            v2 = v2.Revert();
            Len = Slip.ToBytes().Length.Revert();

            var AppDataPath = Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData);
            using (MemoryStream stream = new MemoryStream())
            {
                using (BinaryWriter writer = new BinaryWriter(stream))
                {
                    writer.Write(v2);
                    writer.Write(Len);
                    writer.Write(tmpSlip);
                    if (Directory.Exists(Path.Combine(AppDataPath, "Embarcadero")))
                    {
                        var files = Directory.GetFiles(Path.Combine(AppDataPath, "Embarcadero"), $@".{RadStudioVersion.LicHostPID}_{RadStudioVersion.LicHostSKU}.19*.slip", SearchOption.AllDirectories);
                        if (files.Length > 0 && MessageBox.Show($"删除旧的Slip文件吗？\n{string.Join("\n", files)}", "提示", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
                        {
                            foreach (var f in files)
                            {
                                File.SetAttributes(f, FileAttributes.Archive);
                                File.Delete(f);
                            }
                        }
                        var random = new Random();
                        FileName = $@"{AppDataPath}\Embarcadero\.{RadStudioVersion.LicHostPID}_{RadStudioVersion.LicHostSKU}.19{random.Next(0, 9)}{random.Next(0, 9)}{random.Next(0, 9)}{random.Next(0, 9)}{random.Next(0, 9)}{random.Next(0, 9)}{random.Next(0, 9)}{random.Next(0, 9)}{random.Next(0, 9)}{random.Next(0, 9)}{random.Next(0, 9)}.slip";
                    }
                    else
                    {
                        FileName = Path.Combine(Path.GetDirectoryName(Environment.GetCommandLineArgs()[0]), "RAD Studio Activation.slip");
                    }
                    stream.SaveToFile(FileName);
                    File.SetAttributes(FileName, File.GetAttributes(FileName) | FileAttributes.ReadOnly);
                }
            }

            return true;
        }

        private static void PatchInternal(RadStudioVersion RadStudioVersion, string FileName)
        {
            SHA1 sha1 = SHA1.Create();
            var content = File.ReadAllBytes(FileName);
            var fileSha1 = sha1.ComputeHash(content).ToHex();
            if (fileSha1.ToLower() == RadStudioVersion.mOasisRuntimePatchInfo.Sha1.ToLower())
            {
                content[RadStudioVersion.mOasisRuntimePatchInfo.PatchOffset] = 0xEB;
                File.WriteAllBytes(FileName, content);
            }
        }
        public static void PatchmOasisRuntime(RadStudioVersion RadStudioVersion)
        {
            var AppDataPath = Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData);
            if (RadStudioVersion.SetupProcessName != null)
            {
                var s = DllInject.UnInject(RadStudioVersion.SetupProcessName, "mOasisRuntime.dll");
                if (s != DllInject.DllInjectionResult.Success)
                {
                    MessageBox.Show("未启动安装程序？", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
                    return;
                }
                PatchInternal(RadStudioVersion, DllInject.UnInjectDll);
                s = DllInject.Inject(RadStudioVersion.SetupProcessName, DllInject.UnInjectDll);
                goto done;
            }
            else
            {
                var FileName = Path.Combine(AppDataPath, RadStudioVersion.SetupGUID, @"OFFLINE\mOasisDesigntime.dll\mOasisRuntime.dll");
                if (File.Exists(FileName))
                {
                    PatchInternal(RadStudioVersion, FileName);
                    goto done;
                }
                return;
            }
        done:
            MessageBox.Show("成功应用安装补丁！", "完成", MessageBoxButtons.OK, MessageBoxIcon.Information);

        }

        public static string GetRootDir(RadStudioVersion RadStudioVersion)
        {
            var registry = Registry.LocalMachine;
            var subkey = registry.OpenSubKey($@"SOFTWARE\Embarcadero\BDS\{RadStudioVersion.BDSVersion}", false);
            if (subkey == null)
                return null;
            var RootDir = (string)subkey.GetValue("RootDir");
            return RootDir;
        }

        public static bool PatchFile(RadStudioVersion RadStudioVersion, ref string FileName)
        {
            var RootDir = GetRootDir(RadStudioVersion);
            if (RootDir == null)
                return false;
            var bdsPath = $@"{RootDir}\Bin\bds.exe";
            var licPath = $@"{RootDir}\Bin\LicenseManager.exe";
            if (Directory.Exists(Path.Combine(RootDir, "Bin")) && File.Exists(bdsPath) && File.Exists(licPath))
            {
                var bdsSha1 = TisnUtils.FileSha1(bdsPath).ToLower();
                var licSha1 = TisnUtils.FileSha1(licPath).ToLower();
                if (bdsSha1 == RadStudioVersion.BdsPatchInfo.Sha1.ToLower() && licSha1 == RadStudioVersion.LicenseManagerPatchInfo.Sha1.ToLower())
                {
                    FileName = $@"{RootDir}\Bin\SHFolder.dll";
                    var BDSVersion = double.Parse(RadStudioVersion.BDSVersion);
                    if (BDSVersion >= 21.0)
                    {
                        SHFolderDll.CreateSHFolderDataNew(RadStudioVersion);
                        File.WriteAllBytes(FileName, SHFolderDll.SHFolderDllData_New);
                    }
                    else
                    {
                        SHFolderDll.CreateSHFolderData(RadStudioVersion);
                        File.WriteAllBytes(FileName, SHFolderDll.SHFolderData);
                    }
                    return true;
                }
            }
            return false;
        }
    }
}
